/*
 MIT License
 Copyright (c) 2022 Boy
*/

const 脚本 = `
我有一个字符串「🐔」，等于“坤坤❤️”。
我有一个字符串「🏀」，等于“打篮球”。

输出：“我叫”「🐔」“，我会”「🏀」“。”。
`;

(function (脚本) {
    const 输入流 = 脚本 => {
        let 位置 = 0, 行 = 1, 列 = 0;

        const 下个字符 = () => {
            const 字符 = 脚本.charAt(位置++);
            if (字符 === '\n') {
                行++;
                列 = 0;
            } else 列++;
            return 字符;
        };

        const 观测 = () => 脚本.charAt(位置);

        const 结束 = () => 位置 >= 脚本.length;

        const 异常 = 信息 => {
            throw new Error(`${信息} ${行}:${列}`);
        };

        return {下个字符, 观测, 结束, 异常};
    };

    const 令牌流 = 输入流 => {
        const 关键词 = /(我|有|一|个)/i;
        const 类型 = /(整|数|字|符|串)/i;
        const 空白字符 = /(，| |\t|\n)/i;
        const 整数 = /[0-9一二三四五六七八九十壹贰叁肆伍陆柒捌玖拾]/i;
        const 变量 = /(?!我|有|一|个)[\u4e00-\u9fa5]/i;
        const 标点符号 = /(。|：|「|」)/i;
        const 运算符 = /(等|于)/i;
        let 当前 = null;

        const 是否 = {
            关键词: 字符 => 关键词.test(字符),
            类型: 字符 => 类型.test(字符),
            空白字符: 字符 => 空白字符.test(字符),
            整数: 字符 => 整数.test(字符),
            变量: 字符 => 变量.test(字符),
            标点符号: 字符 => 标点符号.test(字符),
            运算符: 字符 => 运算符.test(字符)
        };

        const 跳过 = {
            批注: () => {
                读取.期间(字符 => 字符 !== '\n');
                输入流.下个字符();
            }
        };

        const 令牌 = {
            关键词: 值 => ({类型: '关键词', 值}),
            字符串: 值 => ({类型: '字符串', 值}),
            整数: 值 => ({类型: '整数', 值}),
            类型: 值 => ({类型: '类型', 值}),
            变量: 名称 => ({类型: '变量', 名称}),
            标点符号: 值 => ({类型: '标点符号', 值}),
            运算符: 值 => ({类型: '运算符', 值})
        };

        const 读取 = {
            下个令牌: () => {
                读取.期间(是否.空白字符);
                if (输入流.结束()) return null;
                const 字符 = 输入流.观测();

                switch (字符) {
                    case '~': {
                        跳过.批注();
                        return 读取.下个令牌();
                    }
                    case '“': {
                        return 读取.字符串();
                    }
                    case '「': {
                        return 读取.引用变量();
                    }
                }

                if (是否.运算符(字符)) return 令牌.运算符(读取.期间(是否.运算符));
                if (是否.关键词(字符)) return 令牌.关键词(读取.期间(是否.关键词));
                if (是否.类型(字符)) return 令牌.类型(读取.期间(是否.类型));
                if (是否.整数(字符)) return 读取.整数();
                if (是否.变量(字符)) return 读取.变量();
                if (是否.标点符号(字符)) return 令牌.标点符号(输入流.下个字符());

                输入流.异常(`意外的令牌 「${字符}」`);
            },
            期间: 断言 => {
                let 字符 = '';
                while (!输入流.结束() && 断言(输入流.观测()))
                    字符 += 输入流.下个字符();
                return 字符;
            },
            捕捉: 结束字符 => {
                let 已捕捉 = false, 捕捉字符 = '';
                输入流.下个字符();
                while (!输入流.结束()) {
                    const 字符 = 输入流.下个字符();
                    if (已捕捉) {
                        捕捉字符 += 字符;
                        已捕捉 = false;
                    } else if (字符 === '\\') {
                        已捕捉 = true;
                    } else if (字符 === 结束字符) {
                        break;
                    } else {
                        捕捉字符 += 字符;
                    }
                }
                return 捕捉字符;
            },
            引用变量: () => 令牌.变量(读取.捕捉('」')),
            变量: () => 令牌.变量(读取.期间(是否.变量)),
            字符串: () => 令牌.字符串(读取.捕捉('”')),
            整数: () => 令牌.整数(读取.期间(是否.整数))
        };

        const 观测 = () => 当前 || (当前 = 读取.下个令牌());

        const 下个令牌 = () => {
            const 令牌 = 当前;
            当前 = null;
            return 令牌 || 读取.下个令牌();
        };

        const 结束 = () => 观测() == null;

        return {下个令牌, 观测, 结束, 是否匹配: 是否, 异常: 输入流.异常};
    };

    const 解析 = 令牌流 => {
        const 令牌 = {
            程序: 程序体 => ({
                '类型': '程序',
                '描述': {
                    '程序体': 程序体
                }
            }),
            声明变量: (名称, 类型, 值) => ({
                '类型': '声明变量',
                '描述': {
                    '名称': 名称,
                    '类型': 类型,
                    '值': 值
                }
            }),
            调用表达式: (名称, 参数) => ({
                '类型': '调用表达式',
                '描述': {
                    '调用': 名称,
                    '参数': 参数
                }
            }),
            获取变量: 名称 => ({
                '类型': '获取变量',
                '描述': {
                    '名称': 名称
                }
            }),
            字符串: 值 => ({
                '类型': '字符串',
                '描述': {
                    '值': 值
                }
            }),
        };

        const 是否 = {
            标点符号: 字符 => {
                const 令牌 = 令牌流.观测();
                return 令牌 && 令牌.类型 === '标点符号' && (!字符 || 令牌.值 === 字符) && 令牌;
            },
            类型: 字符 => {
                const 令牌 = 令牌流.观测();
                return 令牌 && 令牌.类型 === '类型' && (!字符 || 令牌.值 === 字符) && 令牌;
            },
            关键词: 关键词 => {
                const 令牌 = 令牌流.观测();
                return 令牌 && 令牌.类型 === '关键词' && (!关键词 || 令牌.值 === 关键词) && 令牌;
            },
            运算符: 运算符 => {
                const 令牌 = 令牌流.观测();
                return 令牌 && 令牌.类型 === '运算符' && (!运算符 || 令牌.值 === 运算符) && 令牌;
            }
        };

        const 跳过 = {
            标点符号: 字符 => {
                if (是否.标点符号(字符)) 令牌流.下个令牌();
                else 令牌流.异常(`期望标点符号 「${字符}」`);
            },
            类型: 字符 => {
                if (是否.类型(字符)) 令牌流.下个令牌();
                else 令牌流.异常(`期望类型 「${字符}」`);
            }
        };

        const 或许 = {
            调用: 表达式 => 表达式(),
            嵌套: (当前节点, 父节点) => 当前节点
        };

        const 语法分析 = {
            顶层: () => {
                const 语法树 = Array();
                while (!令牌流.结束()) {
                    语法树.push(语法分析.表达式());
                }
                return 令牌.程序(语法树);
            },
            表达式: () => 或许.调用(() => 或许.嵌套(语法分析.原子(), 0)),
            原子: () => 或许.调用(() => {
                if (是否.标点符号('。')) 令牌流.下个令牌();

                if (是否.关键词('我有一个') && 令牌流.下个令牌()) {
                    const 类型令牌 = 令牌流.下个令牌();
                    const 变量令牌 = 令牌流.下个令牌();
                    const 运算符令牌 = 令牌流.下个令牌();
                    const 令牌 = 令牌流.下个令牌();

                    if (类型令牌.类型 === '类型' &&
                        变量令牌.类型 === '变量' &&
                        运算符令牌.类型 === '运算符' &&
                        运算符令牌.值 === '等于' &&
                        (令牌.类型 === '整数' || 令牌.类型 === '字符串')
                    ) return 语法分析.声明变量(变量令牌.名称, 类型令牌.值, 令牌.值);
                }

                const 令牌 = 令牌流.下个令牌();
                if (令牌.类型 === '变量') {
                    const 名称 = 令牌.名称;
                    const 参数 = Array();
                    const 标点符号令牌 = 令牌流.下个令牌();
                    if (标点符号令牌.值 === '：') {
                        while (!是否.标点符号('。')) {
                            const 令牌 = 令牌流.下个令牌();
                            if (令牌 === null) {
                                跳过.标点符号('。');
                                break;
                            }
                            switch (令牌.类型) {
                                case '变量':
                                    参数.push(语法分析.获取变量(令牌.名称));
                                    break;
                                case '字符串':
                                    参数.push(语法分析.字符串(令牌.值));
                                    break;
                                default:
                                    语法分析.意外();
                            }
                        }
                        令牌流.下个令牌();
                        return 语法分析.调用表达式(名称, 参数);
                    }
                }

                语法分析.意外();
            }),
            获取变量: 名称 => 令牌.获取变量(名称),
            字符串: 值 => 令牌.字符串(值),
            调用表达式: (名称, 参数) => 令牌.调用表达式(名称, 参数),
            声明变量: (名称, 类型, 值) => {
                if ((类型 === '整数' && !令牌流.是否匹配.整数(值))) {
                    令牌流.异常(`需要整数，但得到 「${值}」`);
                }
                if ((类型 === '字符串' && 令牌流.是否匹配.整数(值))) {
                    令牌流.异常(`需要字符串，但得到 「${值}」`);
                }
                return 令牌.声明变量(名称, 类型, 值);
            },
            意外: () => 令牌流.异常(`意外的令牌 「${令牌流.观测().值 || 令牌流.观测().名称 || 令牌流.观测().类型}」`)
        };

        return 语法分析.顶层();
    };

    const 执行 = (语句, 环境) => {
        switch (语句.类型) {
            case '程序':
                let 返回值 = false;
                语句.描述.程序体.forEach(语句 => {
                    返回值 = 执行(语句, 环境);
                });
                return 返回值;
            case '字符串':
                return 语句.描述.值;
            case '获取变量':
                return 环境.获取(语句.描述.名称);
            case '声明变量':
                return 环境.设置(语句.描述.名称, 语句.描述.值);
            case '调用表达式':
                let 参数 = String();
                语句.描述.参数.forEach(参数表达式 => 参数 += 执行(参数表达式, 环境));
                return 环境.变量表[语句.描述.调用].call(null, 参数);
            default:
                throw new Error(`无法执行 「${语句.类型}」`);
        }
    };

    function 环境() {
        this.变量表 = Object.create(null);
    }

    环境.prototype = {
        获取: function (名称) {
            if (名称 in this.变量表)
                return this.变量表[名称] || '空';
            throw new Error(`未定义变量 「${名称}」`);
        },
        设置: function (名称, 值) {
            return this.变量表[名称] = 值;
        }
    };

    const 默认环境 = new 环境();

    const 内置方法 = {
        输出: 信息 => {
            console.log(信息 || '空');
        }
    };

    for (let 方法 in 内置方法) {
        默认环境.设置(方法, 内置方法[方法]);
    }

    执行(解析(令牌流(输入流(脚本))), 默认环境);
})(脚本);